package com.nulldev.util.web.HttpClient.clients.legacy;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.List;
import java.util.Map;

import com.nulldev.util.data.Variables;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.io.throttling.BandwidthThrottledInputStream;
import com.nulldev.util.io.throttling.BandwidthThrottler;
import com.nulldev.util.web.HttpClient.HttpResponse;
import com.nulldev.util.web.HttpClient.ResponseFormat;
import com.nulldev.util.web.HttpClient.Version;
import com.nulldev.util.web.HttpClient.util.HttpClientUtil;
import com.nulldev.util.web.HttpClient.util.HttpStreamUtil;
import com.nulldev.util.web.URLUtils.nStreamHandlerFactory;

public class LegacyHttpResponse<T> implements HttpResponse<T> {

	private final HttpURLConnection conn;
	private final ResponseFormat format;
	private final InputStream is;
	private final OutputStream os;
	private final BandwidthThrottler throttler;

	public LegacyHttpResponse(final HttpURLConnection conn, final InputStream is, final OutputStream os, final ResponseFormat format,
			final BandwidthThrottler throttler) {
		this.conn = conn;
		this.format = format;
		this.is = is;
		this.os = os;
		this.throttler = throttler;
	}

	@Override
	public int status() {
		try {
			return this.conn.getResponseCode();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public boolean successful() {
		return this.status() >= 200 && this.status() <= 299;
	}

	@Override
	public ResponseFormat format() {
		return this.format;
	}

	@SuppressWarnings("unchecked")
	@Override
	public T data() {
		if (this.status() == 404 || this.status() == 403) {
			if (this.format == ResponseFormat.BYTES) {
				return (T) new byte[0];
			} else if (this.format == ResponseFormat.STRING || this.format == ResponseFormat.STRING_VIA_STREAM) {
				return (T) "";
			} else if (this.format == ResponseFormat.READER) {
				return (T) new InputStreamReader(nStreamHandlerFactory.BLANK);
			} else {
				return (T) nStreamHandlerFactory.BLANK;
			}
		}
		final String encoding = this.conn.getContentEncoding();

		InputStream output;
		try {
			output = HttpStreamUtil.decode(this.is, encoding);
		} catch (IOException exception) {
			if (!exception.toString().contains("Server returned HTTP response code: 503 for URL")) {
				exception.printStackTrace();
			}
			output = this.conn.getErrorStream();
		}
		if (this.throttler != null)
			output = new BandwidthThrottledInputStream(output, this.throttler);
		try {
			if (this.format == ResponseFormat.STREAM) {
				return (T) output;
			} else if (this.format == ResponseFormat.READER) {
				return (T) new InputStreamReader(output);
			} else if (this.format == ResponseFormat.BYTES) {
				return (T) IOUtils.toByteArray(output);
			} else {
				return (T) new String(IOUtils.toByteArray(output));
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public void close() throws Exception {
		if (this.conn.getDoInput()) {
			IOUtils.closeQuietly(this.is);
		}
		if (this.conn.getDoOutput()) {
			IOUtils.closeQuietly(this.os);
		}
		this.conn.disconnect();
	}

	@Override
	public void adaptiveClose() throws Exception {
		if (this.conn.getDoInput()) {
			IOUtils.closeQuietly(this.is);
		}
		if (this.conn.getDoOutput()) {
			IOUtils.closeQuietly(this.os);
		}
		if (!this.headers().containsKey("connection")
				&& !(this.headers().containsKey("connection") && this.headers().get("connection").contains("keep-alive"))) {
			this.conn.disconnect();
		}
	}

	@Override
	public T dataThenClose() {
		final T data = this.data();
		try {
			this.adaptiveClose();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return data;
	}

	@Override
	public Map<String, List<String>> headers() {
		return this.conn.getHeaderFields();
	}

	@Override
	public String[] cookies() {
		final List<String> o = this.conn.getHeaderFields().get("Set-Cookie");
		return o.toArray(new String[o.size()]);
	}

	@SuppressWarnings("unchecked")
	@Override
	public T data(final boolean avoidDecoding) {
		if (avoidDecoding) {
			try {
				InputStream output = this.conn.getInputStream();
				if (this.throttler != null)
					output = new BandwidthThrottledInputStream(output, this.throttler);
				if (this.format == ResponseFormat.STREAM) {
					return (T) output;
				} else if (this.format == ResponseFormat.READER) {
					return (T) new InputStreamReader(output);
				} else if (this.format == ResponseFormat.BYTES) {
					return (T) IOUtils.toByteArray(output);
				} else {
					return (T) new String(IOUtils.toByteArray(output));
				}
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		} else {
			return this.data();
		}
	}

	@Override
	public T dataThenClose(final boolean avoidDecoding) {
		final T data = this.data(avoidDecoding);
		try {
			this.adaptiveClose();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return data;
	}

	@Override
	public void writeTo(final OutputStream os) throws IOException {
		Variables.requireNonNull(os, "os");
		String encoding = null;
		try {
			encoding = this.conn.getContentEncoding();
		} catch (Exception e) {
		}
		InputStream is = HttpStreamUtil.decode(this.is, encoding);
		if (this.throttler != null)
			is = new BandwidthThrottledInputStream(is, this.throttler);
		IOUtils.autoCopy(is, os);
		IOUtils.closeQuietly(this.is);
	}

	@Override
	public void writeTo(final OutputStream os, final int preferredBufferSize) throws IOException {
		Variables.requireNonNull(os, "os");
		HttpStreamUtil.__check_valid_buffer_size(preferredBufferSize);
		String encoding = null;
		try {
			encoding = this.conn.getContentEncoding();
		} catch (Exception e) {
		}
		InputStream is = HttpStreamUtil.decode(this.is, encoding);
		if (this.throttler != null)
			is = new BandwidthThrottledInputStream(is, this.throttler);
		IOUtils.autoCopyWithBufferSize(is, os, preferredBufferSize);
		IOUtils.closeQuietly(this.is);
	}

	@Override
	public void writeTo(final WritableByteChannel os) throws IOException {
		Variables.requireNonNull(os, "os");
		String encoding = null;
		try {
			encoding = this.conn.getContentEncoding();
		} catch (Exception e) {
		}
		InputStream is = HttpStreamUtil.decode(this.is, encoding);
		if (this.throttler != null)
			is = new BandwidthThrottledInputStream(is, this.throttler);
		final ReadableByteChannel iso = IOUtils.copyMakeChannel(is);
		IOUtils.fastCopy(iso, os);
		IOUtils.closeQuietly(iso);
	}

	@Override
	public Version transportVersion() {
		return Version.HTTP_1_1;
	}

	@Override
	public long length(final boolean raw) {
		if (raw) {
			return this.conn.getContentLengthLong();
		} else {
			return HttpClientUtil.getFallbackLength(this);
		}
	}

}
